package com.zl.es.demo;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zl.es.demo.pojo.Goods;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

public class ElasticDemo {

    private RestHighLevelClient client;

    /**
     * 建立连接
     */
    @Before
    public void init() throws IOException {
        client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("192.168.200.150", 9200, "http")
                )
        );
    }

    /**
     * 关闭客户端连接
     */
    @After
    public void close() throws IOException {
        client.close();
    }

    @Test
    /**
     * 创建索引库
     */
    public void testCreateIndex() throws IOException {
        //1.创建CreateIndexRequest对象，并指定索引库名称
        CreateIndexRequest goods = new CreateIndexRequest("goods");
        //2.索引库添加库结构数据 ，包括但不限于setting 和 mapping
        //备注：Java的字符串 "XXX" ,XXX里面包含<"">,则会被转义处理
        goods.source("{\n" +
                "                \"settings\": {\n" +
                "            \"analysis\": {\n" +
                "                \"analyzer\": {\n" +
                "                    \"my_pinyin\": {\n" +
                "                        \"tokenizer\": \"ik_smart\",\n" +
                "                                \"filter\": [\n" +
                "                        \"py\"\n" +
                "          ]\n" +
                "                    }\n" +
                "                },\n" +
                "                \"filter\": {\n" +
                "                    \"py\": {\n" +
                "                        \"type\": \"pinyin\",\n" +
                "                                \"keep_full_pinyin\": false,\n" +
                "                                \"keep_joined_full_pinyin\": true,\n" +
                "                                \"keep_original\": true,\n" +
                "                                \"limit_first_letter_length\": 16,\n" +
                "                                \"remove_duplicated_term\": true\n" +
                "                    }\n" +
                "                }\n" +
                "            }\n" +
                "        },\n" +
                "        \"mappings\": {\n" +
                "            \"properties\": {\n" +
                "                \"id\": {\n" +
                "                    \"type\": \"keyword\"\n" +
                "                },\n" +
                "                \"name\": {\n" +
                "                    \"type\": \"completion\",\n" +
                "                            \"analyzer\": \"my_pinyin\",\n" +
                "                            \"search_analyzer\": \"ik_smart\"\n" +
                "                },\n" +
                "                \"title\": {\n" +
                "                    \"type\": \"text\",\n" +
                "                            \"analyzer\": \"my_pinyin\",\n" +
                "                            \"search_analyzer\": \"ik_smart\"\n" +
                "                },\n" +
                "                \"price\": {\n" +
                "                    \"type\": \"long\"\n" +
                "                }\n" +
                "            }\n" +
                "        }\n" +
                "}",XContentType.JSON);
        //3.发起请求，得到响应
        CreateIndexResponse response = client.indices().create(goods, RequestOptions.DEFAULT);
        System.out.println("response = " + response.isAcknowledged()); //response.isAcknowledged() 询问索引库是否创建成功

    }

    @Test
    /**
     *  导入文档数据
     */
    public void testBulkDocument() throws IOException {
        //1.准备文档数据  插入数据只能一次
        List<Goods> list = new ArrayList<>();
        list.add(new Goods(1L, "红米9", "红米9手机 数码", 1499L));
        list.add(new Goods(2L, "三星 Galaxy A90", "三星 Galaxy A90 手机 数码 疾速5G 骁龙855", 3099L));
        list.add(new Goods(3L, "Sony WH-1000XM3", "Sony WH-1000XM3 降噪耳机 数码", 2299L));
        list.add(new Goods(4L, "松下剃须刀", "松下电动剃须刀高转速磁悬浮马达", 599L));
        //2.创建BulkRequest对象
        BulkRequest bulkRequest = new BulkRequest();
        //3.创建多个IndexRequest对象 ，将索引库数据添加到BulkRequest
        for (Goods goods : list) {
            bulkRequest.add(new IndexRequest("goods")
                        .id(goods.getId().toString())
                        .source(JSON.toJSONString(goods), XContentType.JSON)
            );
        }
        //4.发起请求，需要请求对象 ，请求对象又需要添加数据
        BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println("bulkResponse.status = " + bulkResponse.status());
    }

    /**
     * 基本查询 myself
     * @throws IOException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @Test
    public void testBasicSearch() throws IOException, InvocationTargetException, IllegalAccessException {
        //1.创建查询对象，并添加条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //1.0 添加高亮条件 .hightLight <---条件有 HightBuilder 构建
        searchSourceBuilder.highlighter(new HighlightBuilder().field("title"));
        //1.1 添加排查条件 .sort  <--- 条件有 <排序 filed ，排序方式 SortOrder.ASC>
        searchSourceBuilder.sort("price", SortOrder.ASC);
        //1.2 查询条件  .query  <---  条件有 QueryBuilder 构建 里面静态方法生成实例
        /*
        *  QueryBuilders.boolQuery() 构建布尔查询对象（多个条件查询，属于链式编程）
        * .must(QueryBuilders.matchQuery("title", "数码")) 条件必须成立 .filter(QueryBuilders.rangeQuery("price").lte(3000))
        */
        searchSourceBuilder.query(QueryBuilders.boolQuery()
                            .must(QueryBuilders.matchQuery("title", "数码"))
                            .filter(QueryBuilders.rangeQuery("price").lte(3000)));
        //1.3 添加聚合条件 .aggregation  条件有  <--- AggregationBuildes 构建 必须是 keyword类型的字符串
        //searchSourceBuilder.aggregation(AggregationBuilders.terms("name"));
        //1.4 添加分页条件
        int page = 1,size = 5;
        int from = (page - 1) * size ; //当前页开始条数
        searchSourceBuilder.size(size);
        searchSourceBuilder.from(from);

        //2.创建搜索请求对象 SearchRquest,并指定索引库名。.source封装查询对象
        SearchRequest searchRequest = new SearchRequest("goods");
        searchRequest.source(searchSourceBuilder);
        //3.发起请求，需要请求对象 ，对象还包含查询json对象SearchSourceBuilder ，
        // 查询对象需要添加条件，条件又有五种 ， “高排查聚分；啊” .hightLight() .sort .query .arrgation .<form ,size>
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        //4.解析结果； 响应结果 有两大模块，查询结果 ，聚合结果 ；查询中又有两部分：总条数 + 命中结果数组<源数据-文档 ，高亮>
        //因此 对应三种方法获得
        SearchHits searchHits = searchResponse.getHits();
        //4.1获取总条数
        TotalHits totalHits = searchHits.getTotalHits();
        System.out.println("totalHits = " + totalHits);
        //4.2获取命中结果数组
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            //获取其中的源数据
            String sourceAsString = hit.getSourceAsString();
            //把源数据 反序列化为 Javabean
            Goods goods = JSONObject.parseObject(sourceAsString, Goods.class);
            //Goods goods = JSON.parseObject(sourceAsString, Goods.class);
            //获取高亮的结果
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            //遍历高亮字段
            for (HighlightField field : highlightFields.values()) {
                //获取字段名
                String name = field.getName();
                //获取字段值
                String filedValues = StringUtils.join(field.getFragments());
                //注入对象中 setProperty()  <--- BeanUtilsBean.getInstance().setProperty(bean, name, value);
                //给一个javabean注入属性和值
                BeanUtils.setProperty(goods,name,filedValues);
            }
            System.out.println("goods = " + goods);
        }
    }

    @Test
    /**
     * 老师代码
     */
    public void testBasicSearchWithSortAndPage() throws IOException, InvocationTargetException,
            IllegalAccessException {
        // 1.创建SearchSourceBuilder对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 1.0.source过滤  todo 起的作用？？？
        sourceBuilder.fetchSource(new String[0], new String[]{"name"});
            // 1.1.添加查询条件QueryBuilders，这里选择布尔查询，查询标题包含“数码”，并且价格小于3000
            // 1.1.1.定义布尔查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            // 1.1.2.添加match查询
        boolQueryBuilder.must(QueryBuilders.matchQuery("title", "数码"));
            // 1.1.3.添加价格过滤
        boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(3000));
        sourceBuilder.query(boolQueryBuilder);
            // 1.2.添加排序、分页等其它条件
        sourceBuilder.sort("price", SortOrder.ASC);
            // 1.3.添加分页条件
        int page = 1, size = 5;
        int from = (page - 1) * size;
        sourceBuilder.from(from);
        sourceBuilder.size(size);
            // 1.4.高亮
        sourceBuilder.highlighter(new HighlightBuilder().field("title"));
            // 2.创建SearchRequest对象，并制定索引库名称
        SearchRequest request = new SearchRequest("goods");
            // 2.1.添加SearchSourceBuilder对象到SearchRequest对象中
        request.source(sourceBuilder);
            // 3.发起请求，得到结果
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 4.解析结果
        SearchHits searchHits = response.getHits();
            // 4.1.获取总条数
        long total = searchHits.getTotalHits().value;
        System.out.println("total = " + total);
            // 4.2.获取SearchHits数组，并遍历
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            // - 获取其中的`_source`，是JSON数据
            String json = hit.getSourceAsString();
            // - 把`_source`反序列化为User对象
            Goods goods = JSON.parseObject(json, Goods.class);
            // 获取高亮结果
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            // 遍历高亮字段
            for (HighlightField field : highlightFields.values()) {
            // 获取字段名
                String fieldName = field.getName();
            // 获取字段值
                String fieldValue = StringUtils.join(field.getFragments());
            // 注入对象中
                BeanUtils.setProperty(goods, fieldName, fieldValue);
            }
            System.out.println("goods = " + goods);
        }
    }

    @Test
    /**
     * suggest请求查询
     */
    public void testSuggest() throws IOException {
        //1.创建查询对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            //1.0 添加条件
                //1.0.0 suggest  <--- SuggestBuilder 构建
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("name_suggest",
                SuggestBuilders.completionSuggestion("name").prefix("s").size(30));
        searchSourceBuilder.suggest(suggestBuilder);
        //2.创建请求对象
        SearchRequest searchRequest = new SearchRequest("goods");
        searchRequest.source(searchSourceBuilder);
        //3.发请求，请求对象并指定索引库名
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        //4.解析结果
        Suggest suggest = searchResponse.getSuggest();
                // 4.1.根据suggest查询名称获取suggest结果
        Suggest.Suggestion<? extends Suggest.Suggestion.Entry<?
                extends Suggest.Suggestion.Entry.Option>> nameSuggest = suggest.getSuggestion("name_suggest");
        // 4.2.遍历结果
        nameSuggest.forEach(suggestion -> {
                // 获取其中的options
            List<? extends Suggest.Suggestion.Entry.Option> options = suggestion.getOptions();
            System.out.println("补全的结果如下： ");
                // 遍历options
            for (Suggest.Suggestion.Entry.Option option : options) {
                Text text = option.getText();
                System.out.println("\t" + text);
            }
        });



    }

    @Test
    /**
     * 异步新增
     */
    public void testAsyncAddDoucment() throws InterruptedException {
        //1.准备文档
        Goods goods = new Goods(5L, "松下电吹风", "松下电吹风 网红电吹风", 1599L);
        //2.创建indexRequest新增请求对象
        //指定新增库名 ，指定新增的id ,指定导入的数据源
        IndexRequest indexRequest = new IndexRequest("goods")
                .id(goods.getId().toString())
                .source(JSON.toJSONString(goods), XContentType.JSON);

        //3.client执行请求
        client.indexAsync(indexRequest, RequestOptions.DEFAULT, new ActionListener<IndexResponse>() {
            /**
             * 执行成功时的回调，参数是IndexResponse结果
             * @param indexResponse 执行结果
             */
            @Override
            public void onResponse(IndexResponse indexResponse) {
                System.out.println("这是成功的回调！ = " + indexResponse);
            }

            /**
             * 执行失败时的回调，参数是异常信息
             * @param e 异常信息
             */
            @Override
            public void onFailure(Exception e) {
                System.out.println("这是失败的回调");
                e.printStackTrace();
            }
        });

        System.out.println("我的异步方法调用完成");
        // 因为我们的程序结束会立即停止，接收不到回调结果，这里我们休眠一下，等待下回调结果
        Thread.sleep(1000L);
    }

    @Test
    /**
     * 异步查询
     */
    public void testAsyncQuery() throws InterruptedException {
        System.out.println("准备开始查询");
        // 准备一个查询文档的请求
        GetRequest request = new GetRequest("goods", "1");
        // 异步查询一个文档，耗时50ms
        client.getAsync(request, RequestOptions.DEFAULT, new ActionListener<GetResponse>() {
            @Override
            public void onResponse(GetResponse response) {
                // 获取source
                String json = response.getSourceAsString();
                // 把json反序列化
                Goods goods = JSON.parseObject(json, Goods.class);
                System.out.println("查询结束，得到结果： " + goods);
            }
            @Override
            public void onFailure(Exception e) {
                e.printStackTrace();
            }
        });
        System.out.println("请求已经发出，等待执行结果！");
        Thread.sleep(2000);

    }

    @Test
    /**
     * 异步删除
     */
    public void testAsyncDeleteDocument() throws InterruptedException {
        //1.创建异步删除请求对象
        DeleteRequest deleteRequest = new DeleteRequest("goods", "s");

        //2.执行异步删除请求
        client.deleteAsync(deleteRequest, RequestOptions.DEFAULT, new ActionListener<DeleteResponse>() {
            /**
             * 执行成功时的回调，参数是IndexResponse结果
             * @param "indexResponse 执行结果"
             */
            @Override
            public void onResponse(DeleteResponse deleteResponse) {
                System.out.println("我是成功的回调！" + deleteResponse);
            }

            /**
             * 执行失败时的回调，参数是异常信息
             * @param e 异常信息
             */
            @Override
            public void onFailure(Exception e) {
                System.out.println("我是shibai的回调！");
            }
        });
        System.out.println("我的异步方法调用完成");
        Thread.sleep(2000);
    }



}

